/*
MIT License

Copyright (c) 2022 МГТУ им. Н.Э. Баумана, кафедра ИУ-6, Михаил Фетисов,

https://bmstu.codes/lsx/simodo/loom
*/

#include "ScriptSemantics_abstract.h"
#include "simodo/interpret/AnalyzeException.h"
#include "simodo/interpret/StackOfNames.h"
#include "simodo/variable/FunctionWrapper.h"
#include "simodo/bormental/DrBormental.h"
#include "simodo/inout/convert/functions.h"
#include "simodo/inout/format/fmt.h"

#include <functional>
#include <iterator>
#include <cassert>

#if __cplusplus >= __cpp_2017
#include <filesystem>
namespace fs = std::filesystem;
#else
#include <experimental/filesystem>
namespace fs = std::filesystem::experimental;
#endif

namespace simodo::interpret
{
    namespace 
    {
        void initializeOperationSwitchboard(std::vector<OperationParser_t> & switchboard)
        {
            switchboard.resize(static_cast<size_t>(ScriptOperationCode::LastOperation), nullptr);

            switchboard[static_cast<size_t>(ScriptOperationCode::None)]           = OperationParser::parseNone;
            switchboard[static_cast<size_t>(ScriptOperationCode::PushConstant)]   = OperationParser::parsePushConstant;
            switchboard[static_cast<size_t>(ScriptOperationCode::PushVariable)]   = OperationParser::parsePushVariable;
            switchboard[static_cast<size_t>(ScriptOperationCode::ObjectElement)]  = OperationParser::parseObjectElement;
            switchboard[static_cast<size_t>(ScriptOperationCode::FunctionCall)]   = OperationParser::parseFunctionCall;
            switchboard[static_cast<size_t>(ScriptOperationCode::ProcedureCheck)] = OperationParser::parseProcedureCheck;
            switchboard[static_cast<size_t>(ScriptOperationCode::Print)]          = OperationParser::parsePrint;
            switchboard[static_cast<size_t>(ScriptOperationCode::Block)]          = OperationParser::parseBlock;
            switchboard[static_cast<size_t>(ScriptOperationCode::Pop)]            = OperationParser::parsePop;

            switchboard[static_cast<size_t>(ScriptOperationCode::Reference)]      = OperationParser::parseReference;
            switchboard[static_cast<size_t>(ScriptOperationCode::ArrayElement)]   = OperationParser::parseArrayElement;

            switchboard[static_cast<size_t>(ScriptOperationCode::ObjectStructure)]  = OperationParser::parseRecordStructure;
            switchboard[static_cast<size_t>(ScriptOperationCode::ArrayStructure)]   = OperationParser::parseArrayStructure;
            switchboard[static_cast<size_t>(ScriptOperationCode::Import)]           = OperationParser::parseImport;
            switchboard[static_cast<size_t>(ScriptOperationCode::Contract)]         = OperationParser::parseContractDefinition;
            switchboard[static_cast<size_t>(ScriptOperationCode::Type)]             = OperationParser::parseContractDefinition;
            switchboard[static_cast<size_t>(ScriptOperationCode::Announcement)]     = OperationParser::parseAnnouncement;
            switchboard[static_cast<size_t>(ScriptOperationCode::Declaration)]      = OperationParser::parseDeclaration;
            switchboard[static_cast<size_t>(ScriptOperationCode::Announcements_Completed)]= OperationParser::parseDeclarationCompletion;
            switchboard[static_cast<size_t>(ScriptOperationCode::PostInitialize)]   = OperationParser::parsePostAssignment;
            
            switchboard[static_cast<size_t>(ScriptOperationCode::Initialize)]         = OperationParser::parseAssignment;
            switchboard[static_cast<size_t>(ScriptOperationCode::Assignment)]         = OperationParser::parseAssignment;
            switchboard[static_cast<size_t>(ScriptOperationCode::GroupInitialize)]    = OperationParser::parseGroupInitialize;

            switchboard[static_cast<size_t>(ScriptOperationCode::AssignmentAddition)] = OperationParser::parseAssignment;
            switchboard[static_cast<size_t>(ScriptOperationCode::AssignmentSubtraction)]= OperationParser::parseAssignment;
            switchboard[static_cast<size_t>(ScriptOperationCode::AssignmentMultiplication)]= OperationParser::parseAssignment;
            switchboard[static_cast<size_t>(ScriptOperationCode::AssignmentDivision)] = OperationParser::parseAssignment;
            switchboard[static_cast<size_t>(ScriptOperationCode::AssignmentModulo)]   = OperationParser::parseAssignment;

            switchboard[static_cast<size_t>(ScriptOperationCode::Plus)]           = OperationParser::parseUnary;
            switchboard[static_cast<size_t>(ScriptOperationCode::Minus)]          = OperationParser::parseUnary;
            switchboard[static_cast<size_t>(ScriptOperationCode::Not)]            = OperationParser::parseUnary;

            switchboard[static_cast<size_t>(ScriptOperationCode::Or)]             = OperationParser::parseLogical;
            switchboard[static_cast<size_t>(ScriptOperationCode::And)]            = OperationParser::parseLogical;

            switchboard[static_cast<size_t>(ScriptOperationCode::Equal)]          = OperationParser::parseCompare;
            switchboard[static_cast<size_t>(ScriptOperationCode::NotEqual)]       = OperationParser::parseCompare;
            switchboard[static_cast<size_t>(ScriptOperationCode::Less)]           = OperationParser::parseCompare;
            switchboard[static_cast<size_t>(ScriptOperationCode::LessOrEqual)]    = OperationParser::parseCompare;
            switchboard[static_cast<size_t>(ScriptOperationCode::More)]           = OperationParser::parseCompare;
            switchboard[static_cast<size_t>(ScriptOperationCode::MoreOrEqual)]    = OperationParser::parseCompare;

            switchboard[static_cast<size_t>(ScriptOperationCode::Addition)]       = OperationParser::parseArithmetic;
            switchboard[static_cast<size_t>(ScriptOperationCode::Subtraction)]    = OperationParser::parseArithmetic;
            switchboard[static_cast<size_t>(ScriptOperationCode::Multiplication)] = OperationParser::parseArithmetic;
            switchboard[static_cast<size_t>(ScriptOperationCode::Division)]       = OperationParser::parseArithmetic;
            switchboard[static_cast<size_t>(ScriptOperationCode::Modulo)]         = OperationParser::parseArithmetic;
            switchboard[static_cast<size_t>(ScriptOperationCode::Power)]          = OperationParser::parseArithmetic;

            switchboard[static_cast<size_t>(ScriptOperationCode::Ternary)]        = OperationParser::parseConditional;
            switchboard[static_cast<size_t>(ScriptOperationCode::If)]             = OperationParser::parseConditional;

            switchboard[static_cast<size_t>(ScriptOperationCode::FunctionDefinition)]= OperationParser::parseFunctionDefinition;
            switchboard[static_cast<size_t>(ScriptOperationCode::FunctionDefinitionEnd)]= OperationParser::parseFunctionDefinitionEnd;
            switchboard[static_cast<size_t>(ScriptOperationCode::FunctionTethered)]= OperationParser::parseFunctionTethered;

            switchboard[static_cast<size_t>(ScriptOperationCode::Return)]         = OperationParser::parseReturn;
            switchboard[static_cast<size_t>(ScriptOperationCode::ReturnExpression)]= OperationParser::parseReturn;

            switchboard[static_cast<size_t>(ScriptOperationCode::For)]            = OperationParser::parseFor;
            switchboard[static_cast<size_t>(ScriptOperationCode::While)]          = OperationParser::parseWhile;
            switchboard[static_cast<size_t>(ScriptOperationCode::DoWhile)]        = OperationParser::parseDoWhile;
            switchboard[static_cast<size_t>(ScriptOperationCode::Break)]          = OperationParser::parseBreak;
            switchboard[static_cast<size_t>(ScriptOperationCode::Continue)]       = OperationParser::parseContinue;

            switchboard[static_cast<size_t>(ScriptOperationCode::Apply)]          = OperationParser::parseApply;
            // switchboard[static_cast<size_t>(ScriptOperationCode::AutoDefine)]     = OperationParser::parseAutoDefine;
            switchboard[static_cast<size_t>(ScriptOperationCode::AutoDeclaration)]= OperationParser::parseDeclaration;
            switchboard[static_cast<size_t>(ScriptOperationCode::CheckState)]     = OperationParser::parseCheckState;

            switchboard[static_cast<size_t>(ScriptOperationCode::FiberMake)]    = OperationParser::parseFiberOperations;
            switchboard[static_cast<size_t>(ScriptOperationCode::FiberFlow)]    = OperationParser::parseFiberOperations;
            switchboard[static_cast<size_t>(ScriptOperationCode::FiberWait)]    = OperationParser::parseFiberOperations;
            switchboard[static_cast<size_t>(ScriptOperationCode::FiberPush)]    = OperationParser::parseFiberOperations;
            switchboard[static_cast<size_t>(ScriptOperationCode::FiberPull)]    = OperationParser::parseFiberOperations;
            // switchboard[static_cast<size_t>(ScriptOperationCode::FiberCut)]     = OperationParser::parseFiberOperations;
        }

    }

    ScriptSemantics_abstract::ScriptSemantics_abstract(ModuleManagement_interface & module_management)
        : _module_management(module_management)
    {
        initializeOperationSwitchboard(_operation_switchboard);
        // _accumulated_contract_stack.push_back({});

        initiateCallbacks();
    }

    ScriptSemantics_abstract::~ScriptSemantics_abstract()
    {
    }

    void ScriptSemantics_abstract::reset()
    {
    }

    InterpretState ScriptSemantics_abstract::before_start() 
    { 
        return InterpretState::Flow; 
    }

    InterpretState ScriptSemantics_abstract::before_finish(InterpretState state) 
    { 
        return state; 
    }

    bool ScriptSemantics_abstract::isOperationExists(ast::OperationCode operation_code) const
    {
        size_t operation_index = static_cast<size_t>(operation_code);

        return operation_index < _operation_switchboard.size() 
            && _operation_switchboard[operation_index] != nullptr;
    }

    InterpretState ScriptSemantics_abstract::performOperation(const ast::Node & op) 
    {
        if (!isOperationExists(op.operation()))
            throw bormental::DrBormental("ScriptSemantics_abstract::performOperation", 
                                inout::fmt("Invalid operation index %1")
                                    .arg(op.operation()));

        return _operation_switchboard[static_cast<size_t>(op.operation())](*this,op);
    }

    std::string ScriptSemantics_abstract::makeModulePath(const inout::Token & path, const std::string & parent_path)
    {
        if (path.type() != inout::LexemeType::Annotation)
            return inout::toU8(path.lexeme());

        if (path.lexeme()[0] == u'/')
            return fs::current_path().concat(path.lexeme()).string();

        return fs::path(parent_path).parent_path().append(path.lexeme()).string();
    }

    bool ScriptSemantics_abstract::prepareForSource()
    {
        assert(_cycle_data_stack.back().source.value().isArray() || _cycle_data_stack.back().source.value().isObject());

        variable::index_t counter = _cycle_data_stack.back().source_counter;

        if ((_cycle_data_stack.back().source.value().isArray() && counter >= _cycle_data_stack.back().source.value().getArray()->values().size())
         || (_cycle_data_stack.back().source.value().isObject() && counter >= _cycle_data_stack.back().source.value().getObject()->variables().size()))
        {
            stack().clearBoundary(_cycle_data_stack.back().boundary_index);
            _cycle_data_stack.pop_back();
            return false;
        }

        variable::Value value;

        if (_cycle_data_stack.back().source.value().isArray())
            value = _cycle_data_stack.back().source.value().getArray()->getValueByIndex(counter);
        else if (_cycle_data_stack.back().source.value().isObject()) {
            std::shared_ptr<variable::Object> object = _cycle_data_stack.back().source.value().getObject();

            value = object->getVariableByIndex(counter).value();
        }
        else
            assert(false);

        try
        {
            if (!_cycle_data_stack.back().typed_variable.value().isNull()
             && value.type() != _cycle_data_stack.back().typed_variable.type())
                value = inter().expr().convertVariable({{}, value}, _cycle_data_stack.back().typed_variable.type()).value();
        }
        catch(...)
        {
            stack().clearBoundary(_cycle_data_stack.back().boundary_index);
            _cycle_data_stack.pop_back();
            throw;
        }
        
        variable::Variable & variable = stack().variable(_cycle_data_stack.back().variable_index);

        variable.value() = value;

        return true;
    }

    bool ScriptSemantics_abstract::hidden(const variable::Variable & var)
    {
        const variable::Value & hidden_value = var.spec().object()->find(variable::SPEC_HIDDEN);

        return hidden_value.isBool() && hidden_value.getBool();
    }

    bool ScriptSemantics_abstract::tethered(const variable::Variable & var)
    {
        const variable::Value & tethered_value = var.spec().object()->find(variable::SPEC_TETHERED);

        return tethered_value.isBool() && tethered_value.getBool();
    }

    void ScriptSemantics_abstract::setParentObjectForFunction(variable::Variable & function, 
                                                            std::shared_ptr<variable::Object> parent_object)
    {
        if (function.origin().type() == variable::ValueType::Function) {
            std::shared_ptr<variable::Object> function_object  = function.origin().value().getObject();
            assert(function_object->variables().size() > 1);
            if (function_object->variables()[0].type() == variable::ValueType::IntFunction)
                function_object->variables()[0].value().getIntFunctionParent() = parent_object;
        }
    }

    variable::Variable & ScriptSemantics_abstract::prepareFiberVariableOrigin()
    {
        variable::Variable & fiber_variable_ref    = stack().variable(stack().top());
        variable::Variable & fiber_variable_origin = fiber_variable_ref.origin();
        std::string                object_name_text      = fiber_variable_ref.name().empty()
                                                         ? inout::toU8(fiber_variable_origin.name()) 
                                                         : inout::toU8(fiber_variable_ref.name());

        if (!object_name_text.empty())
            object_name_text = " '" + object_name_text + "'";

        if (!fiber_variable_origin.value().isObject())
            throw AnalyzeException( "ScriptSemantics_abstract::executeFiberWait", 
                                    fiber_variable_ref.location().makeLocation(inter().files()), 
                                    inout::fmt("Only a variable with an object type can act as a fiber"));

        std::shared_ptr<variable::Object> object = fiber_variable_origin.value().getObject();

        const ChildFlowInfo * p_info = inter().parallelize().findChildFlowInfo(fiber_variable_origin);
        if (checkInterpretType(InterpretType::Preview) && !p_info)
            throw AnalyzeException( "ScriptSemantics_abstract::executeFiberWait", 
                                    fiber_variable_ref.location().makeLocation(inter().files()), 
                                    inout::fmt("Object %1 have not fiber")
                                        .arg(object_name_text));

        return fiber_variable_origin;
    }    

}
